use cmsis_rtos2_rs::os_delay;
use cmsis_rtos2_rs::static_mem::EventFlags;
use periph_utils::bootloader::boot2;
use utils::MutCell;

use crate::app;
use crate::app::proto::cmd::OTA_COMPLETE;
use crate::app::{
    error::AppErr,
    proto::{
        cmd,
        ty::{CONNECT, CONNECT_ACK, PING, POLL, POLL_ACK, PONG, REQ, REQ_ACK, RES_ACK, SIMPLE_REQ},
        DataAck, Empty, EmptyResponse, Proto, RequestRef,
    },
    task::{get_run_flag, post_request},
};

use super::ota;

const FLAG_RES_ACK: u32 = 0x01;

pub struct Dispatcher {
    res_ack_flags: EventFlags,
    connect_flag: MutCell<bool>,
}

pub static DISPATCHER: Dispatcher = Dispatcher::uninit();

impl Dispatcher {
    const fn uninit() -> Self {
        Self {
            connect_flag: MutCell::const_new(false),
            res_ack_flags: EventFlags::uninit(),
        }
    }

    pub unsafe fn init_once(&self) {
        self.res_ack_flags.init_once("res_ack_flags");
    }

    fn do_simple_req(request: &RequestRef) -> Result<(), AppErr> {
        #[cfg(feature = "cp_drop")]
        return app::drop::exec::on_simple_req(&request);

        #[cfg(feature = "cp_cargo")]
        return app::cargo::exec::on_simple_req(&request);
    }

    pub fn exec(&self, request: RequestRef) {
        let ty = request.ty();

        match ty {
            SIMPLE_REQ => {
                if !request.is_req_length() {
                    return;
                }
                let cmd = request.cmd_or_other();

                let ret = match cmd {
                    cmd::OTA_START => request
                        .parse_body()
                        .and_then(|pkg_size| ota::on_start(pkg_size)),
                    cmd::OTA_TRANSLATE => {
                        request.parse_body().and_then(|pkg| ota::on_translate(pkg))
                    }
                    cmd::OTA_COMPLETE => request
                        .parse_body()
                        .and_then(|md5_data| ota::on_complete(md5_data)),

                    _ => Self::do_simple_req(&request),
                };

                send_to_ipc(&EmptyResponse::new_simple(&request, &ret));
                
                if (cmd == OTA_COMPLETE) && ret.is_ok() {
                    os_delay(500);
                    unsafe { boot2(BOOTLOADER_ORIGIN as u32); }
                }
            }

            REQ => {
                if !request.is_req_length() {
                    return;
                }
                send_to_ipc(&Empty::new_with_request(&request, REQ_ACK));

                if !(*self.connect_flag) {
                    return;
                }
                unsafe {
                    *self.connect_flag.as_mut() = false;
                }

                post_request(request.to_owned());
            }

            CONNECT => {
                unsafe {
                    *self.connect_flag.as_mut() = true;
                }
                send_to_ipc(&Empty::new_with_request(&request, CONNECT_ACK));
            }

            RES_ACK => {
                self.res_ack_flags.set_flags(FLAG_RES_ACK);
            }

            POLL => {
                let ret = get_run_flag();
                send_to_ipc(&DataAck::new(&request, POLL_ACK, ret));
            }

            PING => {
                send_to_ipc(&Empty::new(
                    PONG,
                    request.seq(),
                    request.dst(),
                    request.src(),
                ));
            }

            _ => {}
        }
    }
}

pub fn send_to_ipc<'a>(x: &impl Proto<'a>) {
    #[cfg(feature = "cp_drop")]
    {
        use super::super::driver::cmd_uart;
        cmd_uart::write(x);
    }

    #[cfg(not(feature = "cp_drop"))]
    {
        use super::super::driver::cmd_can;
        cmd_can::write(x);
    }
}

pub fn send_to_ipc_res<'a>(x: &impl Proto<'a>) {
    DISPATCHER.res_ack_flags.clear_flags(FLAG_RES_ACK);
    for _ in 0..3 {
        send_to_ipc(x);
        match DISPATCHER.res_ack_flags.wait_any_flags(FLAG_RES_ACK, 500) {
            Ok(_) => {
                return;
            }
            Err(_) => {
            }
        };
    }
}
